
/**
 * This file handles the gameplay side of the Game of Nim
 * and monitors all gameplay activities.
 * @author James Wilkinson
 * Edge Hill University
 * December 2013
 * 21604819
 */

package Server;

import Client.GONClientInterface;
import java.rmi.RemoteException;
import java.util.Random;



public class GameOfNim extends GONServer implements Runnable {
    
    // These represent the client's which are player 1 and player 2 for the game
    private static GONClientInterface player1 = null;
    private static GONClientInterface player2 = null;
    
    // Keeps track of the current number of marbles left
    private int numberOfMarbles = -1;
    
    // These two variables represent IDs for the game levels. Purely
    // for the sake of ease of reference
    private final int GAME_OF_NIM_LEVEL_EASY        = 0;
    private final int GAME_OF_NIM_LEVEL_HARD        = 1;
    
    // These 2 monitor the current state of the game
    private final int GAME_OF_NIM_STATE_IDLE        = 0;
    private final int GAME_OF_NIM_STATE_RUNNING     = 1;
    
    // Monitors the current state of the game.
    private int currentState = GAME_OF_NIM_STATE_IDLE;
    
    // This boolean determines if the game that is running is single player or not
    private boolean isSinglePlayer  = false;
    
    /**
     * When this method is called, the player objects are updated
     * and the game begins!
     * A random integer is generated representing a level for the number
     * of marbles that should be displayed.
     */
    public GameOfNim() throws RemoteException
    {
        // Initialise the game and set up all the data
        initGameOfNim();
  
    }
   
    
    /**
     * This method initialises the Game of Nim 
     * by setting all the data for a new game
     */
    public void initGameOfNim() throws RemoteException {
        player1 = getPlayer1Pointer();
        player2 = getPlayer2Pointer();
        
        setState(GAME_OF_NIM_STATE_RUNNING);
        
        sendMsgToAll("*** Game of Nim starting...");
        // Generate a random level and a random number of marbles.
        // Levels:
        // 0 - GAME_OF_NIM_LEVEL_EASY
        // 1 - GAME_OF_NIM_LEVEL_HARD
        generateRandomNumberOfMarbles(new Random().nextInt(2));
        if(getNumberOfMarbles() < 20) {
            sendMsgToAll("Game difficulty: Easy");
        }else{
            sendMsgToAll("Game difficulty: Hard");
        }
        sendMsgToAll("NUMBER OF MARBLES: "+getNumberOfMarbles());  
        
        /**
         * If the player 2 pointer is null then this is a single
         * player game that is being run
         */
        if(player2 == null) {
            isSinglePlayer = true;
            System.out.println("Game of nim initialised: SINGLEPLAYER");
            // Start the thread that pick a random player to go first
            new Thread(this).start();   
            return;
        }
        
        // This is the multiplayer game
        
        System.out.println("Game of Nim initialised with 2 clients connected.");
        
        // Send a message to each player explaining that the game has
        // started.

        player1.sendMsg("You are player 1.");
        player2.sendMsg("You are player 2.");   
        // Start the thread that pick a random player to go first
        new Thread(this).start();   
    }
    
    
    
    /**
     * This method calculates a random player to go first and initialises
     * the listening for client input
     */
    public void run() {
        try {
            // If this is single player, player 1 goes first
            if(isSinglePlayer == true) {
                setClientsTurn(player1);
                return;
            }
            // Choose a random player to go first
            int randPlayer =(Math.random()<0.5)?0:1;
            if(randPlayer == 0) {
                sendMsgToAll("IT IS PLAYER 1's TURN TO GO FIRST!");
                setClientsTurn(player1);
            } else {
                sendMsgToAll("IT IS PLAYER 2's TURN TO GO FIRST!");
                setClientsTurn(player2);
                return;
            }
        } catch (RemoteException ex) {
        }
    }
    
    /**
     * Set the current state of the program/
     * 0 = GAME_OF_NIM_STATE_IDLE 
     * 1 = GAME_OF_STATE_RUNNING
     * @param state the state to set
     */
    private void setState(int state) throws RemoteException{
        this.currentState = state;
        // Update this state for both the players too
        if(player1 != null) {
            player1.setGameOfNimState(state);
        }
        if(player2 != null) {
            player2.setGameOfNimState(state);
        }
            
            
    }
    
    public int getState() {
        return this.currentState;
    }
    
    
    
    /**
     * This method determines which client's turn it is to select how
     * many marbles they wish to take from the pile.
     * @param player the player whos turn it is next
     * @throws RemoteException 
     */
    public void setClientsTurn(GONClientInterface player) throws RemoteException {
        if(player == player1) {
            if(player2 != null) {
                player2.setClientsTurn(false);
            }
        } else if (player == player2) {
            player1.setClientsTurn(false);
        }
        player.setClientsTurn(true);
        player.monitorClientInput();
    }
    
    /**
     * This method is called when it is the computers turn
     * to make a move and play against the client. It calculates
     * a valid random number of marbles to deduct
     */
    public void calculateComputerMove() throws RemoteException {
        
        sendMsgToAll("Computers turn..");
        
        // If there are more than 2 marbles remaining then
        // calculate a random legit move the computer can make
        if(getNumberOfMarbles() > 3) {
            int max = getNumberOfMarbles() / 2;
            int min = 1;

            Random rand = new Random();
            int random_integer = rand.nextInt(max-min) + min;

            deductMarbles(random_integer);
            sendMsgToAll("COMPUTER MOVE: Removed "+random_integer+ " marbles.");
        } else {
            // If there are exactly 2 marbles remaining, the computer can make a move to win the game!
            deductMarbles(2);
           sendMsgToAll("COMPUTER MOVE: Removed 2 marbles.");
        }

        sendMsgToAll("Number of marbles remaining: " + getNumberOfMarbles());
        // If there are no more marbles remaining the computer has
        // won so start a new game.
        if(getNumberOfMarbles() == 0) {
            sendMsgToAll("LOSER!!! Computer won!");
            sendMsgToAll("-----------------------------");
            sendMsgToAll("GAME OF NIM: STARTING NEW GAME!!");
            initGameOfNim();
            return;
        }
        // Ok the computer hasn't won, the client gets another go!
        player1.setClientsTurn(true);
    }
        
    /**
     * This callback is called when the client inputs a value into the console
     * and sends it to the server. It deals with validation and checks if its
     * the players turn and if so deducts the number of marbles accordingly
     * @param player The player who is sending the string 
     * @param message the message that is being sent from the client
     */
    public void onClientResponse(GONClientInterface player, String message) throws RemoteException {
        // If the client inputs a value when the game isn't running do not process it.
        if(getState() == GAME_OF_NIM_STATE_IDLE) {
            System.out.println("Client response:  Game is idle - response ignored.");
            return;
        }
        
        System.out.println("OnClientResponse: "+message);       
        try{
            int marblesToDeduct = Integer.parseInt(message);
                            
            if(marblesToDeduct < 1 || (marblesToDeduct > getNumberOfMarbles()/2) && getNumberOfMarbles() > 2) {
                player.sendMsg("Invalid number of marbles! It must be between 1 and "+getNumberOfMarbles()/2);
                return;
            }
            System.out.println("Input from client: "+marblesToDeduct);
            
            // Ensure that the player has made a valid move!
            if(marblesToDeduct > getNumberOfMarbles()){
                throw new RemoteException("Attempting to deduct too many marbles");
            }
            
            deductMarbles(marblesToDeduct);
            
                        
            sendMsgToAll("Player has chosen to remove "+marblesToDeduct +"marbles.");
            sendMsgToAll("Number of marbles remaining: "+getNumberOfMarbles());            
            
            // There is always guranteed to be a player1 so disable
            // this clients turn
            player1.setClientsTurn(false);
            
            // If there are no marbles left we have a winner!!!
            if(getNumberOfMarbles() == 0)
            {
                setState(GAME_OF_NIM_STATE_IDLE);
                player.sendMsg("WINNER!!!!!!!!!!!!! Starting new game..");
                // If this isn't single player clarify which player won!
                if(isSinglePlayer == false) {
                    if(player.equals(player1)) {
                        sendMsgToAll("Player 1 has won!!!!");
                    } else {
                        sendMsgToAll("Player 2 has won!!!");
                    }
                    player2.setClientsTurn(false);
                } else {
                    // This is single player. The player still won though!
                    player.sendMsg("WINNER!!!!! You beat the computer!!");
                    player.sendMsg("Starting new game...");
                }
                


                // Start a new instance of the game
                sendMsgToAll("-----------------------------");
                sendMsgToAll("GAME OF NIM: STARTING NEW GAME");
                initGameOfNim();
                return;
            }
            
            
            // OK the player has made thier choice.
            // If this is single player, its the computers turn to make a choice!
            if(isSinglePlayer == true) {
                calculateComputerMove();
                return;
            }
            
            
            // OK. The player has made their choice. Now make it the next players
            // turn during multiplayer.
            if(isSinglePlayer == false) {
                // Player 1 has chosen a marble. Now its player 2s turn!
                if(player.equals(player1)) {
                    System.out.println("Player 1 has chosen. Moving to player 2");
                    sendMsgToAll("It is now player 2's turn...");
                    player1.sendMsg("Waiting for player 2...");
                    setClientsTurn(player2);
                } else if (player.equals(player2)){ // Player 2 has chosen a marbles. Now its player 1s turn!
                    System.out.println("Player 2 has chosen. Moving to player 1");
                    sendMsgToAll("It is now player 1's turn...");
                    player2.sendMsg("Waiting for player 1...");
                    setClientsTurn(player1);
                }
            }
        } catch (Exception e) {
            player.sendMsg("You must input a number!! Try again. Make sure there are enough marbles left!");        
        }
   
    }
    
    
    
    /*
     * This method will generate a random number of marbles
     * to start off with based on the level parameter that is passed
     * to it. If the level is 0 (GAME_OF_NIM_LEVEL_EASY) a random integer
     * between 2 and 20 is generated. If the level is 1 (GAME_OF_NIM_LEVEL_HARD)
     * a random integer between 2 and 100 is generated. If the level that is being
     * passed is invalid it will assign -1 to the variable.
     * @param int level 
     */
    public void generateRandomNumberOfMarbles(int level) {
        switch(level) {
            case GAME_OF_NIM_LEVEL_EASY: {
                numberOfMarbles = new Random().nextInt(18) + 5;
                break;
            }
            case GAME_OF_NIM_LEVEL_HARD: {
                numberOfMarbles = new Random().nextInt(98) + 5;
                break;
            }
            // No valid level has been passed return -1 
            default:
                numberOfMarbles = -1;
        }
    }
    
    /**
     * This method returns the number of marbles that are left in the pile
     * @return integer numberOfMarbles
     */
    private int getNumberOfMarbles() {
        return numberOfMarbles;
    }
    
    /**
     * This method deducts the specified amount of marbles from the pile.
     * It has validation to ensure that too many marbles cannot be deducted resulting
     * in the amount being more than the amount of marbles available
     * @param amount
     * @return integer 1 if success 0 if fail
     */
    private void deductMarbles(int amount) {
        numberOfMarbles -= amount;
    }
    
    /**
     * This method will send a message to both player 1 and player 2
     * if the multiplayer game is running. If the multiplayer game is not
     * running it will just display the message for player 1 and the server
     * @param msg 
     */
    public void sendMsgToAll(final String msg) throws RemoteException {
        player1.sendMsg(msg);
        // Support for single player!
        if(player2 != null) {
            player2.sendMsg(msg);
        }
        System.out.println(msg);
    }
}